// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.neuronbit.businessflow.intellij.ui.projectView;

import com.intellij.ide.projectView.PresentationData;
import com.intellij.ide.projectView.ProjectViewNode;
import com.intellij.ide.projectView.ViewSettings;
import com.intellij.ide.projectView.impl.nodes.BasePsiNode;
import com.intellij.ide.projectView.impl.nodes.ClassTreeNode;
import com.intellij.ide.projectView.impl.nodes.PsiFileNode;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.navigation.NavigationItemFileStatus;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.vcs.FileStatus;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.neuronbit.businessflow.intellij.BpmFileType;
import org.jetbrains.annotations.NotNull;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * The type Flow node.
 */
public class FlowNode extends ProjectViewNode<Flow> {
    private final Collection<BasePsiNode<? extends PsiElement>> myChildren;

    /**
     * Instantiates a new Flow node.
     *
     * @param project      the project
     * @param value        the value
     * @param viewSettings the view settings
     */
    public FlowNode(Project project, @NotNull Object value, ViewSettings viewSettings) {
        this(project, (Flow) value, viewSettings, getChildren(project, (Flow) value, viewSettings));
    }

    /**
     * Instantiates a new Flow node.
     *
     * @param project      the project
     * @param value        the value
     * @param viewSettings the view settings
     * @param children     the children
     */
    public FlowNode(Project project, @NotNull Flow value, ViewSettings viewSettings, Collection<BasePsiNode<? extends PsiElement>> children) {
        super(project, value, viewSettings);
        myChildren = children;
    }

    @Override
    @NotNull
    public Collection<BasePsiNode<? extends PsiElement>> getChildren() {
        return myChildren;
    }

    @Override
    public String getTestPresentation() {
        return "Flow:" + getValue().getName();
    }

    @Override
    public boolean contains(@NotNull VirtualFile file) {
        for (final AbstractTreeNode aMyChildren : myChildren) {
            ProjectViewNode treeNode = (ProjectViewNode) aMyChildren;
            if (treeNode.contains(file)) return true;
        }
        return false;
    }

    @Override
    public void update(@NotNull PresentationData presentation) {
        if (getValue() == null || !getValue().isValid()) {
            setValue(null);
        } else {
            presentation.setPresentableText(getValue().getName());
            presentation.setIcon(BpmFileType.INSTANCE.getIcon());
        }
    }

    @Override
    protected boolean shouldPostprocess() {
        return true;
    }

    @Override
    public boolean someChildContainsFile(VirtualFile file) {
        return contains(file);
    }

    @Override
    public void navigate(final boolean requestFocus) {
        getValue().navigate(requestFocus);
    }

    @Override
    public boolean canNavigate() {
        final Flow value = getValue();
        return value != null && value.canNavigate();
    }

    @Override
    public boolean canNavigateToSource() {
        final Flow value = getValue();
        return value != null && value.canNavigateToSource();
    }

    @Override
    public FileStatus getFileStatus() {
        for (BasePsiNode<? extends PsiElement> child : myChildren) {
            final PsiElement value = child.getValue();
            if (value == null || !value.isValid()) continue;
            final FileStatus fileStatus = NavigationItemFileStatus.get(child);
            if (fileStatus != FileStatus.NOT_CHANGED) {
                return fileStatus;
            }
        }
        return FileStatus.NOT_CHANGED;
    }

    @Override
    public boolean canHaveChildrenMatching(final Condition<? super PsiFile> condition) {
        for (BasePsiNode<? extends PsiElement> child : myChildren) {
            if (condition.value(child.getValue().getContainingFile())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Construct form node abstract tree node.
     *
     * @param classToBind the class to bind
     * @param project     the project
     * @param settings    the settings
     * @return the abstract tree node
     */
    public static AbstractTreeNode constructFormNode(final PsiClass classToBind, final Project project, final ViewSettings settings) {
        final Flow flow = new Flow(classToBind);
        final Collection<BasePsiNode<? extends PsiElement>> children = getChildren(project, flow, settings);
        return new FlowNode(project, flow, settings, children);
    }

    private static Collection<BasePsiNode<? extends PsiElement>> getChildren(final Project project, final Flow flow, final ViewSettings settings) {
        final Set<BasePsiNode<? extends PsiElement>> children = new LinkedHashSet<>();
        children.add(new ClassTreeNode(project, flow.getClassToBind(), settings));
        for (PsiFile formBoundToClass : flow.getFormFiles()) {
            children.add(new PsiFileNode(project, formBoundToClass, settings));
        }
        return children;
    }
}
